package ogs.lib.mina.core.service
{
	import flash.errors.IOError;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.utils.Timer;
	import flash.utils.clearTimeout;
	import flash.utils.setTimeout;
	
	import org.osflash.thunderbolt.Logger;
	
	import ogs.lib.mina.core.filterchain.DefaultIoFilterChain;
	import ogs.lib.mina.core.filterchain.IoFilterChain;
	
	public class IoConnector
	{
		private var _ioHandler:IoHandler;
		private var _socket:Socket = null;
		
		private var _host:String = "";
		private var _port:int = 0;
		
		private var _autoReconnect:Boolean = true;
		private var _reconnectMaxCount:int = 5;
		private var _reconnectDelay:int = 500;
		private var _timeoutDelay:int = 15000;
		
		private var _connecting:Boolean = false;
		private var _reconnectCount:int = 0;
		private var _reconnectHandler:uint = 0;
		private var _timeoutHandler:uint = 0;
		
		private var _buffer:ByteArray;
		private var _filterChain:IoFilterChain;

		public function IoConnector()
		{
			_socket = new Socket();
			_socket.endian = Endian.LITTLE_ENDIAN;
			addSocketEventListener();
			
			_buffer = new ByteArray();
			_buffer.endian = Endian.LITTLE_ENDIAN;
			
			_filterChain = new DefaultIoFilterChain(this);
		}
		
		public function connect() : void
		{
			if (_connecting)
			{
				Logger.warn("the socket is already connecting, operation aborded.");
				return;
			}

			if (_socket.connected)
			{
				Logger.info("the socket is already connected, disconnecting old connections.");
				_socket.close();
			}

			_reconnectCount = 0;
			tryConnect();
		}
		
		public function closeSocket() : void
		{
			clearTimeout(_timeoutHandler);
			clearTimeout(_reconnectHandler);
			_connecting = false;
			
			Logger.info("the socket is closing.");
			
			getFilterChain().fireFilterClose();
		}
		
		public function closeSocketDirectly():void
		{
			clearTimeout(_timeoutHandler);
			clearTimeout(_reconnectHandler);
			
			if (_socket.connected)
				_socket.close();
			
			Logger.info("the socket is closed.");
		}

		///////////////////////////////////////////////////////////////////////
		
		private function addSocketEventListener():void
		{
			_socket.addEventListener(Event.CONNECT, connectHandler, false, 0, true);
			_socket.addEventListener(Event.CLOSE, closeHandler, false, 0, true);
			_socket.addEventListener(IOErrorEvent.IO_ERROR, errorHandler, false, 0, true);
			_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler, false, 0, true);
			_socket.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler, false, 0, true);
		}
		
		private function removeSocketEventListener():void
		{
			_socket.removeEventListener(Event.CONNECT, connectHandler);
			_socket.removeEventListener(Event.CLOSE, closeHandler);
			_socket.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
			_socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
			_socket.removeEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
		}
		
		private function tryConnect():void
		{
			_connecting = true;
			_socket.connect(_host, _port);
			
			clearTimeout(_timeoutHandler);
			_timeoutHandler = setTimeout(timeoutClosure, _timeoutDelay);
		}
		
		private function connectHandler(event:Event):void
		{
			clearTimeout(_timeoutHandler);
			clearTimeout(_reconnectHandler);
			_connecting = false;
			_reconnectCount = 0;
			
			Logger.info("the socket connects server successfully.");
			this.getFilterChain().fireSessionOpened();
		}
		
		private function closeHandler(event:Event):void
		{
			clearTimeout(_timeoutHandler);
			clearTimeout(_reconnectHandler);
			_connecting = false;
			
			Logger.info("the socket is closed by server.");
			this.getFilterChain().fireSessionClosed();
			
			if (_autoReconnect)
			{
				_reconnectHandler = setTimeout(reconnectClosure, _reconnectDelay);
			}
		}

		private function errorHandler(event:Event):void
		{
			clearTimeout(_timeoutHandler);
			clearTimeout(_reconnectHandler);
			_connecting = false;
			
			if (event.type == SecurityErrorEvent.SECURITY_ERROR)
			{
				Logger.warn((event as SecurityErrorEvent).text);
				this.getFilterChain().fireExceptionCaught(new Error((event as SecurityErrorEvent).text));
			}
			else if (event.type == IOErrorEvent.IO_ERROR)
			{
				Logger.warn((event as IOErrorEvent).text);
				this.getFilterChain().fireExceptionCaught(new Error((event as IOErrorEvent).text));
			}
			
			if (!_socket.connected && _autoReconnect)
			{
				_reconnectHandler = setTimeout(reconnectClosure, _reconnectDelay);
			}
		}

		private function timeoutClosure():void
		{
			clearTimeout(_reconnectHandler);
			if (_socket.connected)
				return;

			_connecting = false;
			Logger.warn("the socket connecting operation timeout.");
			
			if (_autoReconnect)
			{
				_reconnectHandler = setTimeout(reconnectClosure, _reconnectDelay);
			}
		}

		private function reconnectClosure():void
		{
			if (_autoReconnect)
			{
				if (_reconnectCount < _reconnectMaxCount)
				{
					_reconnectCount++;
					tryConnect();
				}
			}
		}
		
		///////////////////////////////////////////////////////////////////////

		public function setRemoteAddress(host:String, port:int):void
		{
			_host = host;
			_port = port;
		}
		
		public function setHandler(handler:IoHandler):void
		{
			_ioHandler = handler;
		}
		
		public function getHandler():IoHandler
		{
			return _ioHandler;
		}
		
		public function get reconnectDelay():int
		{
			return _reconnectDelay;
		}
		
		public function set reconnectDelay(value:int):void
		{
			_reconnectDelay = value;
		}
		
		public function get reconnectMaxCount():int
		{
			return _reconnectMaxCount;
		}
		
		public function set reconnectMaxCount(value:int):void
		{
			_reconnectMaxCount = value;
		}
		
		public function get timeoutDelay():int
		{
			return _timeoutDelay;
		}
		
		public function set timeoutDelay(value:int):void
		{
			_timeoutDelay = value;
		}
		
		public function get autoReconnect():Boolean
		{
			return _autoReconnect;
		}
		
		public function set autoReconnect(value:Boolean):void
		{
			_autoReconnect = value;
		}
		
		public function get connecting():Boolean
		{
			return _connecting;
		}
		
		public function get connected():Boolean
		{
			return _socket.connected;
		}
		
		///////////////////////////////////////////////////////////////////////
		
		private function dataHandler(event:ProgressEvent):void
		{
//			_buffer.position = 0;
//			_buffer.length = 0;
			
			var _buffer:ByteArray = new ByteArray();
			_socket.readBytes(_buffer);
			
			getFilterChain().fireMessageReceived(_buffer);
		}
		
		public function getFilterChain():IoFilterChain
		{
			return _filterChain;
		}

		public function send(message:Object):void
		{
			getFilterChain().fireFilterWrite(message);
		}
		
		public function sendRawData(data:ByteArray):void
		{
			try
			{
				_socket.writeBytes(data);
				_socket.flush();
			}
			catch (e:IOError)
			{
				Logger.error(e.toString());
			}
		}

	}
}

